using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

/* DNN Specific modules */
using DotNetNuke.Entities.Portals;
using DotNetNuke.Common;
using DotNetNuke.Common.Utilities;
using DotNetNuke.Entities.Tabs;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Modules.Definitions;
using DotNetNuke.Modules.Admin;
using DotNetNuke.Services.Localization;

/* articles specific references */
using Ventrian.NewsArticles;

/* ifinity specific references */
using iFinity.DNN.Modules.GoogleSiteMap;


namespace iFinity.DNN.Modules.GoogleSiteMap
{
    public class ArticlesGoogleSiteMapProvider : GoogleSiteMapProvider 
    {
        #region protected members
        protected int _tabID = 0;
        protected bool _singlePagesOnly;
        protected bool _featuredOnly;
        protected bool _notFeaturedOnly;
        protected bool _showHiddenAndExpired;
        protected bool _useShortUrls;
        protected bool _showCategories;
        protected string _articleCallType;
        protected int _maxNumArticles =-1;
        protected bool _showSecured = false;
        #endregion

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(name, config);
            if (config["articleCallType"] != null)
                _articleCallType = (string)config["articleCallType"];
            else
                _articleCallType = "simple";

            if (config["singlePagesOnly"] != null)
            {
                _singlePagesOnly = Convert.ToBoolean(config["singlePagesOnly"]);
            }
            if (config["showCategories"] != null)
            {
                _showCategories = Convert.ToBoolean(config["showCategories"]);
            }
            if (config["maxNumArticles"] != null)
            {
                string raw = config["maxNumArticles"];
                int val = 0;
                if (Int32.TryParse(raw, out val))
                {
                    _maxNumArticles = val;
                }
                else
                    _maxNumArticles = Null.NullInteger;
            }
            if (config["featuredOnly"] != null)
            {
                string raw = config["featuredOnly"];
                if (bool.TryParse(raw, out _featuredOnly) == false)
                {
                    _featuredOnly = false;
                }
            }
            else
                _featuredOnly = false;

            if (config["showHiddenAndExpired"] != null)
            {
                string raw = config["showHiddenAndExpired"];
                if (bool.TryParse(raw, out _showHiddenAndExpired) == false)
                {
                    _showHiddenAndExpired = false;
                }
            }
            else
                _showHiddenAndExpired = false;

            if (config["useShortUrls"] != null)
            {

                string raw = config["useShortUrls"];
                if (bool.TryParse(raw, out _useShortUrls) == false)
                {
                    _useShortUrls = false;
                }
            }
            else
                _useShortUrls = false;
            //634 : allow secured articels to be shown
            if (config["showSecured"] != null)
            {
                string raw = config["showSecured"];
                if (bool.TryParse(raw, out _showSecured) == false)
                    _showSecured = false;
            }
            else
                _showSecured = false;

        }
        /// <summary>
        /// The 'main' public method - this will be called by the base GoogleSiteMap provider, and expects a list of SitePage objects
        /// to be returned.
        /// </summary>
        /// <param name="siteURL">The URL of the tab so we can identify the site.</param>
        /// <returns>List of SitePage Objects, one for each individual URL identified within this tab/module combination</returns>
        public override List<SitePage> SitePages(string siteURL)
        {
            List<SitePage> sitePages = new List<SitePage>();
            //don't use this call in this page
            return sitePages;
        }
        public override List<SitePage> SitePagesForTab(PortalAliasInfo portalAlias, TabInfo tab)
        {
            //return GetSitePages(portalAlias, tab, null);
            return null;
        }
        public override List<SitePage> SitePagesForTab(PortalAliasInfo portalAlias, TabInfo tab, DotNetNuke.Services.Localization.Locale locale)
        {
            //return GetSitePages(portalAlias, tab, locale);
            return null;
        }
        public override List<SitePage> SitePagesForTab(PortalAliasInfo portalAlias, TabInfo tab, Locale locale, int sitemapNum, int urlCount, int startingCount, int endingCount)
        {
            return GetSitePages(portalAlias, tab, locale, sitemapNum, urlCount, startingCount, endingCount);
        }

        private List<SitePage> GetSitePages(PortalAliasInfo portalAlias, TabInfo tab, Locale locale, int sitemapNum, int urlCount, int startingCount, int endingCount)
        {
            List<SitePage> sitePages = new List<SitePage>();
            try
            {
                _friendlyURLs = (DotNetNuke.Entities.Host.HostSettings.GetHostSetting("UseFriendlyUrls") == "Y");
                //635: get the sitemap page priority from the tab if it is avialable
                _defaultPagePriority = base.GetTabPriority(_defaultPagePriority, tab);

                _tabID = tab.TabID;
                sitePages = GetPageURLsForArticles(portalAlias, tab, locale, sitemapNum, urlCount, startingCount, endingCount);
            }
            catch (Exception ex)
            {
                sitePages.Add(new SitePageException(ex, this.Debug));
                if (this.Debug)
                    this.DebugMessage("Exception:" + ex.Message + " Trace: " + ex.StackTrace);
            }
            return sitePages;
        }
        /// <summary>
        /// Returns a list of URL's for the possible combination of articles on this page/tab.
        /// </summary>
        /// <param name="siteURL">the URL for the site</param>
        /// <returns>List of SitePage objects</returns>
        private List<SitePage> GetPageURLsForArticles(PortalAliasInfo portalAlias, TabInfo tab, Locale locale, int sitemapNum, int urlCount, int startingCount, int endingCount)
        {
            base.SetBaseURL(portalAlias.HTTPAlias); //get the base url and portal alias url
            //initialise the master collection of SitePage objects
            List<SitePage> pageURLs = new List<SitePage>();
            //get the tab, portal and module information
            _portalAliasURL = portalAlias.HTTPAlias;
            if (portalAlias == null)
                throw new Exception("No PortalInfo found for URL (" + _portalAliasURL + ")");
            ModuleController modCtl = new ModuleController();
            ModuleDefinitionController modDefs = new ModuleDefinitionController();
            //get the list of modules for the page
            tab.Modules = modCtl.GetPortalTabModules(portalAlias.PortalID, tab.TabID);
            if (tab.Modules != null)
            {
                foreach (ModuleInfo mod in tab.Modules)
                {
                    //check each module on the page, looking for the article-specific ones
                    List<SitePage> modulePages = new List<SitePage>();  //instantiate new list of pages
                    ModuleDefinitionInfo modDef = modDefs.GetModuleDefinition(mod.ModuleDefID); //get the module definition for this module
                    string modDefFriendlyName = modDef.FriendlyName;
                    if (base.Name.Contains(modDefFriendlyName))  //check that the module name matches with the name of this provider
                    {
                        //it does, so add in the page URLS per article
                        pageURLs.AddRange(GetLatestNewsArticles(modDef, mod, tab, locale, sitemapNum, urlCount, startingCount, endingCount));
                    }
                    
                }
            }
            return pageURLs;

        }
        /// <summary>
        /// This method gets the List of SitePages for the tab/module combination, where an Articles module 
        /// has been found
        /// </summary>
        /// <param name="modDef">The moduleDef object for the module</param>
        /// <param name="mod">The module Object for the module</param>
        /// <param name="tab">The tab object for the page/tab</param>
        /// <returns>List of SitePage objects</returns>
        private List<SitePage> GetLatestNewsArticles(ModuleDefinitionInfo modDef, ModuleInfo mod, TabInfo tab, Locale locale, int sitemapNum, int urlCount, int startingCount, int endingCount)
        {
            List<SitePage> pageURLs = new List<SitePage>();
            if ((tab.IsVisible || (tab.IsVisible == false && _includeHiddenPages == true))
             && (tab.DisableLink == false || (tab.DisableLink == true && _includeDisabledPages == true)))
            {

                ModuleController mc = new ModuleController();
                //get the tab module settings
                Hashtable settings = mc.GetTabModuleSettings(mod.TabModuleID);
                //for this tabModule, find any filtering settings
                string catKey = "Categories" + tab.TabID.ToString();
                List<int> categoryIds = new List<int>();
                int authorId = Null.NullInteger;
                if (settings.ContainsKey(catKey))
                {
                    string categories = (string)settings[catKey];
                    string[] cats = categories.Split(',');
                    foreach (string cat in cats)
                    {
                        int catId = -1;
                        if (Int32.TryParse(cat, out catId))
                            categoryIds.Add(catId);
                    }
                }
                if (settings.ContainsKey("Author"))
                {
                    string authorRaw = (string)settings["Author"];
                    if (!Int32.TryParse(authorRaw, out authorId))
                    {
                        authorId = Null.NullInteger;
                    }
                }

                IList articles = null;
                ArticleController vac = new ArticleController();
                bool byCategories = false;
                if (categoryIds.Count == 1 && categoryIds[0] == -1 && authorId == Null.NullInteger)
                    byCategories = false;
                else
                    byCategories = true;
            
                bool matchAll = false;
                int[] categoriesParm;
                //determine if we are going to search by category, or just get all articles.
                //-1 in the category Id means 'all categories' has been ticked in the 'category filter'
                if (categoryIds.Count ==0 || categoryIds[0] == -1)//695 : no categories list meant index out of range error
                {
                    categoriesParm = null;
                    matchAll = true;
                }
                else
                    categoriesParm = categoryIds.ToArray();

                int pageNum = 1; 
                int pageSize = endingCount - urlCount; //don't retrieve more than is necessary
                //call the news articles module to get the list of articles
                //634 : allow for secured articles to be output into the sitemap
                articles = GetArticleList(mod.ModuleID, categoriesParm, matchAll, _maxNumArticles, Null.NullInteger, true, false, DateTime.Now, Null.NullString, false, _showHiddenAndExpired, authorId, _featuredOnly, _notFeaturedOnly, false, !_showSecured , "StartDate", "DESC",  Null.NullInteger, Null.NullInteger, 1, 1000, DateTime.Now, Null.NullDate);
            
                //articles = ac.GetArticleList(mod.ModuleID, categoryIds.ToArray(),false, _maxNumArticles, Null.NullInteger, true, DateTime.Now, Null.NullString,false, _showHiddenAndExpired, Null.NullInteger, _featuredOnly, _notFeaturedOnly, Null.NullInteger, Null.NullInteger);
                DateTime latestOverall = Convert.ToDateTime("01 Jan 1900");
                if (articles != null)
                {
                    foreach (ArticleInfo article in articles)
                    {
                        if (article.IsSecure == false)// don't output secure articles
                        {
                            //we've got the articles here, so call down and get the individual list of sitePage objects
                            //for each article in the articles module
                            pageURLs.AddRange(CreatePagesFromArticle(article, tab, mod, locale));
                            //keep track of the most recent update overall
                            if (article.LastUpdate > latestOverall)
                                latestOverall = article.LastUpdate;
                        }
                    }
                }
                if (_showCategories)
                {
                    //we also need to add an entry for the categories page
                    string[] queryStringItems = new string[2];
                    queryStringItems[0] = "articleType";
                    queryStringItems[1] = "Categories";
                    string catURL = MakeURL(tab, queryStringItems, string.Empty, locale);
                    SitePage categoriesPages = new SitePage(catURL, latestOverall, this.DefaultPageUpdateFrequency, _defaultPagePriority);
                    pageURLs.Add(categoriesPages);
                }
            }
            return pageURLs;

        }
        private void GetPageNumAndSize(int moduleId, int sitemapNum, int urlCount, int startingCount, int endingCount, out int pageNum, out int pageSize, out int pagesToGet)
        {
            //get the range of remaining values
            pageNum = 1; pageSize = 1; pagesToGet = 1;
            
            //now, did we do this once before? 
            string key = "pageSize:" + moduleId.ToString();
            string rawVal = (string)DotNetNuke.Common.Utilities.DataCache.GetCache(key);
            if (!int.TryParse(rawVal, out pageSize))
            {
                pageSize = endingCount - urlCount; //this is how many articles (max) we can generate for this sitemap
            }
            else
            {
                //the page size has been retrieved.  
                //so we look at how many records to retrieve (all possible) and then
                //work out how many pages to retrieve
                int totalRecords = endingCount - urlCount;
                pagesToGet = (totalRecords + pageSize - 1) / pageSize; // get the number of pages needed
                //the page num depends on the sitemapnum.  SItemap 1 *always*
            }
            if (sitemapNum == 1)//only store the page size for the first sitemap
                DotNetNuke.Common.Utilities.DataCache.SetCache(key, pageSize.ToString());

            //string thisPage = (string)DotNetNuke.Common.Utilities.DataCache.GetCache(key);
            //if (sitemapNum > 1)
            //{
            //    string lastPage = (string)DotNetNuke.Common.Utilities.DataCache.GetCache(prevPage);
            //    if (lastPage != null)
            //    {

            //    }
            //}
            //if (thisPage == null)
            //{
            //    //nothing there

                
            //}

            ////form up the value store
            //thisPage = PageNumSizeEtc(pageNum, pageSize, pagesToGet);

            

        }
        private string PageNumSizeEtc(int pageNum, int pageSize, int pagesToGet)
        {
            return pageNum.ToString() + ":" + pageSize.ToString() + ":" + pagesToGet.ToString();
        }
        private void PageNumSizeEtc(string value,out int pageNum, out int pageSize, out int pagesToGet)
        {
            pageNum = 1;
            pagesToGet = 1;
            pageSize = 0;
            if (value != null)
            {
                string[] values = value.Split(',');
                if (values.GetUpperBound(0) > 0)
                {
                    int.TryParse(values[0], out pageNum);
                    int.TryParse(values[1], out pageSize);
                    int.TryParse(values[2], out pagesToGet);
                }
            }
        }
        private IList GetArticleList(int moduleId, bool isApproved)
        {
            ArticleController vac = new ArticleController();
            IList articles = null;
            //find all methods in the type
            MethodInfo[] methods = vac.GetType().GetMethods();
            Dictionary<string, Type> types = null;
            foreach (MethodInfo method in methods)
            {
                if (method.Name == "GetArticleList")
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    if (parameters.GetUpperBound(0) == 1)
                    {
                        if (parameters[0].Name.ToLower() == "moduleid"
                        && parameters[1].Name.ToLower() == "isapproved")
                        {
                            //this is the call
                            List<object> parmValues = new List<object>();
                            parmValues.Add(moduleId);
                            parmValues.Add(isApproved);
                            articles = (IList)method.Invoke(vac, parmValues.ToArray ());
                            break;
                        }
                    }
                }
            }
            return articles;
        }
        private IList GetArticleList(int moduleId, int[] categoryIds, bool matchAll
                                       , int maxNumArticles, int maxAge, bool isApproved, bool isDraft
                                       , DateTime startDate, string keywords, bool showPending
                                       , bool showExpired, int authorId
                                       , bool featuredOnly, bool notFeaturedOnly
                                       , bool showSecuredONly, bool showNotSecuredOnly
                                       , string sortBy, string sortDirection, int month, int year
                                       , int pageNumber, int pageSize, DateTime currentDate, DateTime agedDate)
        {
            //this whole method exists because the VEntrian Article Module does not
            //use versioning, and frequently breaks binary compatibility with older
            //versions when new version are released.  Because of this, the module
            //is called using reflection, so that this single provider cna support 
            //multiple news articles versions.
            //Yes, it is inefficient compared to directly compiled references,
            //but that's the way it is.
            ArticleController vac = new ArticleController();
            IList articles = null;
            //find all methods in the type
            MethodInfo[] methods = vac.GetType().GetMethods();
            Dictionary<string, Type> types = null;
            foreach (MethodInfo method in methods)
            {
                //look for the 'GetArticleList' method, and when found, match the correct overload
                //for the version (we guess the version based on the overload, because the
                //product version, assembly version and file version are not set in the Articles assembly
                if (method.Name == "GetArticleList")
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    bool matched = false;
                    //match the types and the available methods for different versions, starting with the latest and working backwards                    
                    types = v00703Types();
                    int vernum = 00703;
                    matched = CompareTypeList(types, parameters);

                    if (!matched)
                    {
                        types = v00615Types();
                        vernum = 00615;
                        matched = CompareTypeList(types, parameters);
                    }
                    if (!matched)
                    {
                        types = v00610Types();
                        vernum = 00610;
                        matched = CompareTypeList(types, parameters);
                    }
                    if (!matched)
                    {
                        types = v00601Types();
                        vernum = 00601;
                        matched = CompareTypeList(types, parameters);
                    }
                        
                    if (matched)
                    {
                        //this method takes the supplied parameters (which is the superset for the latest version)
                        //and puts them into an array of values suitable for calling the ArticleController instance.
                        object[] parmValues = FillParameters(vernum, types, moduleId, categoryIds, matchAll
                                               , maxNumArticles, maxAge, isApproved, isDraft
                                               , startDate, keywords, showPending
                                               , showExpired, authorId
                                               , featuredOnly, notFeaturedOnly
                                               , showSecuredONly, showNotSecuredOnly
                                               , sortBy, sortDirection, month, year, pageNumber, pageSize);
                        //call by reflection
                        articles = (IList)method.Invoke(vac, parmValues);
                        break;
                    }  
                }
            }
            return articles;
        }

        private object[] FillParameters(int vernum, Dictionary<string, Type> types 
            , int moduleId, int[] categoryIds, bool matchAll, int maxCount, int maxAge
            , bool isApproved, bool isDraft, DateTime startDate, string keywords
            , bool showPending, bool showExpired, int authorId, bool featuredOnly
            , bool notFeaturedOnly, bool showSecuredOnly, bool showNotSecuredOnly
            , string sortBy, string sortDirection, int month, int year, int pageNumber, int pageSize)
        {

            ArrayList values = new ArrayList();
            if (vernum > 0700)
            {
                /* new values ver 07.03 */
                /*public System.Collections.Generic.List<Ventrian.NewsArticles.ArticleInfo> GetArticleList(
             * int moduleID, System.DateTime currentDate, System.DateTime agedDate, 
             * int[ ] categoryID, bool matchAll, int maxCount, 
             * int pageNumber, int pageSize, string sortBy, string sortDirection, 
             * bool isApproved, bool isDraft, string keywords, int authorID, 
             * bool showPending, bool showExpired, bool showFeaturedOnly, bool showNotFeaturedOnly, 
             * bool showSecuredOnly, bool showNotSecuredOnly, string articleIDs, ref int totalRecords)
                Member of Ventrian.NewsArticles.ArticleController*/
                //0 - moduleid
                values.Add(moduleId);
                if (types.ContainsKey("currentDate"))
                    values.Add(DateTime.Now);
                if (types.ContainsKey("agedDate"))
                    values.Add(Null.NullDate);

                values.Add(categoryIds);
                if (types.ContainsKey("matchAll"))
                    values.Add(matchAll);
                if (types.ContainsKey("maxCount"))
                    values.Add(maxCount);

                if (types.ContainsKey("pageNumber"))
                    values.Add(pageNumber);
                if (types.ContainsKey("pageSize"))
                    values.Add(pageSize);
                if (types.ContainsKey("sortBy"))
                    values.Add(sortBy);
                if (types.ContainsKey("sortDirection"))
                    values.Add(sortDirection);

                if (types.ContainsKey("isApproved"))
                    values.Add(isApproved);
                if (types.ContainsKey("isDraft"))
                    values.Add(isDraft);
                if (types.ContainsKey("keywords"))
                    values.Add(keywords);
                if (types.ContainsKey("authorId"))
                    values.Add(authorId);

                if (types.ContainsKey("showPending"))
                    values.Add(showPending);
                if (types.ContainsKey("showExpired"))
                    values.Add(showExpired);
                if (types.ContainsKey("showFeaturedOnly"))
                    values.Add(featuredOnly);
                if (types.ContainsKey("showNotFeaturedOnly"))
                    values.Add(notFeaturedOnly);

                if (types.ContainsKey("showSecuredOnly"))
                    values.Add(showSecuredOnly);
                if (types.ContainsKey("showNotSecuredOnly"))
                    values.Add(showNotSecuredOnly);
                if (types.ContainsKey("articleIds"))
                    values.Add(Null.NullString);
                if (types.ContainsKey("totalRecords"))
                    values.Add(0);
            }
            else
            {
                //0 - moduleid
                values.Add(moduleId);
                //1 - categoryIds
                values.Add(categoryIds);
                if (types.ContainsKey("matchAll"))
                    values.Add(matchAll);
                if (types.ContainsKey("maxCount"))
                    values.Add(maxCount);
                if (types.ContainsKey("maxAge"))
                    values.Add(maxAge);
                if (types.ContainsKey("isApproved"))
                    values.Add(isApproved);
                if (types.ContainsKey("isDraft"))
                    values.Add(isDraft);
                if (types.ContainsKey("startDate"))
                    values.Add(startDate);
                if (types.ContainsKey("keywords"))
                    values.Add(keywords);
                if (types.ContainsKey("showPending"))
                    values.Add(showPending);
                if (types.ContainsKey("showExpired"))
                    values.Add(showExpired);
                if (types.ContainsKey("authorId"))
                    values.Add(authorId);
                if (types.ContainsKey("showFeaturedOnly"))
                    values.Add(featuredOnly);
                if (types.ContainsKey("showNotFeaturedOnly"))
                    values.Add(notFeaturedOnly);
                if (types.ContainsKey("showSecuredOnly"))
                    values.Add(showSecuredOnly);
                if (types.ContainsKey("showNotSecuredOnly"))
                    values.Add(showNotSecuredOnly);
                if (types.ContainsKey("sortBy"))
                    values.Add(sortBy);
                if (types.ContainsKey("sortDirection"))
                    values.Add(sortDirection);
                if (types.ContainsKey("month"))
                    values.Add(month);
                if (types.ContainsKey("year"))
                    values.Add(year);
            }
            return values.ToArray();
        }
        private bool CompareTypeList(Dictionary<string, Type> types, ParameterInfo[] parameters)
        {
            bool same=false;
            if (types.Count == parameters.GetUpperBound(0) + 1)
            {
                same = true; int i = 0;
                foreach (Type type in types.Values)
                {
                    ParameterInfo parameter = parameters[i];
                    if (type != parameter.ParameterType)
                    {
                        //have to get through the entire list and match all to be the same
                        same = false;
                        break;
                    }
                    i++;
                }
            }
            return same;
        }
        private Dictionary<string, Type> v00601Types()
        {
            //public System.Collections.ArrayList GetArticleList(int moduleID, int[] categoryID
            //, int maxCount, int maxAge, bool isApproved, System.DateTime startDate
            //, string keywords, bool showHiddenAndExpired, int authorID
            //, bool showFeaturedOnly, string sortBy, int month, int year)
            Dictionary<string, Type> types = new Dictionary<string, Type>();
            types.Add("moduleId",typeof(int));  //moduleid
            types.Add("categoryId",typeof(int[])); //categoryid
            //types.Add("matchAll",typeof(bool)); //matchAll
            types.Add("maxCount",typeof(int));  //maxcount
            types.Add("maxAge",typeof(int)); //maxage
            types.Add("isApproved",typeof(bool));//isapproved
            //types.Add("isDraft", typeof(bool));//isDraft
            types.Add("startDate", typeof(DateTime));//startDate
            types.Add("keywords",typeof(string)); //keywords
            //types.Add("showPending",typeof(bool)); //showPending
            types.Add("showExpired",typeof(bool)); //showExpired
            types.Add("authorId",typeof(int)); //authorId
            types.Add("showFeaturedOnly",typeof(bool)); //showfeaturedonly
            //types.Add("showNotFeaturedOnly",typeof(bool)); //shownotfeaturedonly
            //types.Add("showSecuredOnly",typeof(bool)); //showSEcuredOnly
            //types.Add("showNotSecuredOnly",typeof(bool)); //showNotsecuredonly
            types.Add("sortBy",typeof(string)); //sortBy
            //types.Add("sortDirection",typeof(string)); //sortDirection
            types.Add("month",typeof(int)); //month
            types.Add("year",typeof(int)); //year
            return types;
        }
        private Dictionary<string, Type> v00610Types()
        {
            //public System.Collections.ArrayList GetArticleList(int moduleID, int[ ] categoryID
            //, bool matchAll, int maxCount, int maxAge, bool isApproved, bool isDraft
            //, System.DateTime startDate, string keywords, bool showPending, bool showExpired
            //, int authorID, bool showFeaturedOnly, bool showNotFeaturedOnly, string sortBy
            //, string sortDirection, int month, int year)
            Dictionary<string, Type> types = new Dictionary<string, Type>();
            types.Add("moduleId", typeof(int));  //moduleid
            types.Add("categoryId", typeof(int[])); //categoryid
            types.Add("matchAll",typeof(bool)); //matchAll
            types.Add("maxCount", typeof(int));  //maxcount
            types.Add("maxAge", typeof(int)); //maxage
            types.Add("isApproved", typeof(bool));//isapproved
            types.Add("isDraft", typeof(bool));//isDraft
            types.Add("startDate", typeof(DateTime));//startDate
            types.Add("keywords", typeof(string)); //keywords
            types.Add("showPending",typeof(bool)); //showPending
            types.Add("showExpired", typeof(bool)); //showExpired
            types.Add("authorId", typeof(int)); //authorId
            types.Add("showFeaturedOnly", typeof(bool)); //showfeaturedonly
            types.Add("showNotFeaturedOnly",typeof(bool)); //shownotfeaturedonly
            //types.Add("showSecuredOnly",typeof(bool)); //showSEcuredOnly
            //types.Add("showNotSecuredOnly",typeof(bool)); //showNotsecuredonly
            types.Add("sortBy", typeof(string)); //sortBy
            types.Add("sortDirection",typeof(string)); //sortDirection
            types.Add("month", typeof(int)); //month
            types.Add("year", typeof(int)); //year
            return types;
        }
        private Dictionary<string, Type> v00615Types()
        {
            //Public Function GetArticleList(ByVal moduleID As Integer, ByVal categoryID As Integer()
            //, ByVal matchAll As Boolean, ByVal maxCount As Integer, ByVal maxAge As Integer
            //, ByVal isApproved As Boolean, ByVal isDraft As Boolean, ByVal startDate As DateTime
            //, ByVal keywords As String, ByVal showPending As Boolean, ByVal showExpired As Boolean
            //, ByVal authorID As Integer, ByVal showFeaturedOnly As Boolean, ByVal showNotFeaturedOnly As Boolean
            //, ByVal showSecuredOnly As Boolean, ByVal showNotSecuredOnly As Boolean, ByVal sortBy As String
            //, ByVal sortDirection As String, ByVal month As Integer, ByVal year As Integer) As ArrayList
            Dictionary<string, Type> types = new Dictionary<string, Type>();
            types.Add("moduleId", typeof(int));  //moduleid
            types.Add("categoryId", typeof(int[])); //categoryid
            types.Add("matchAll",typeof(bool)); //matchAll
            types.Add("maxCount", typeof(int));  //maxcount
            types.Add("maxAge", typeof(int)); //maxage
            types.Add("isApproved", typeof(bool));//isapproved
            types.Add("isDraft", typeof(bool));//isDraft
            types.Add("startDate", typeof(DateTime));//startDate
            types.Add("keywords", typeof(string)); //keywords
            types.Add("showPending",typeof(bool)); //showPending
            types.Add("showExpired", typeof(bool)); //showExpired
            types.Add("authorId", typeof(int)); //authorId
            types.Add("showFeaturedOnly", typeof(bool)); //showfeaturedonly
            types.Add("showNotFeaturedOnly",typeof(bool)); //shownotfeaturedonly
            types.Add("showSecuredOnly",typeof(bool)); //showSEcuredOnly
            types.Add("showNotSecuredOnly",typeof(bool)); //showNotsecuredonly
            types.Add("sortBy", typeof(string)); //sortBy
            types.Add("sortDirection",typeof(string)); //sortDirection
            types.Add("month", typeof(int)); //month
            types.Add("year", typeof(int)); //year
            return types;
        }
        private Dictionary<string, Type> v00703Types()
        {
            /*public System.Collections.Generic.List<Ventrian.NewsArticles.ArticleInfo> GetArticleList(
             * int moduleID, System.DateTime currentDate, System.DateTime agedDate, 
             * int[ ] categoryID, bool matchAll, int maxCount, 
             * int pageNumber, int pageSize, string sortBy, string sortDirection, 
             * bool isApproved, bool isDraft, string keywords, int authorID, 
             * bool showPending, bool showExpired, bool showFeaturedOnly, bool showNotFeaturedOnly, 
             * bool showSecuredOnly, bool showNotSecuredOnly, string articleIDs, ref int totalRecords)
                Member of Ventrian.NewsArticles.ArticleController*/

            Dictionary<string, Type> types = new Dictionary<string, Type>();
            types.Add("moduleId", typeof(int));  //moduleid
            types.Add("currentDate", typeof(DateTime)); //currentDate
            types.Add("agedDate", typeof(DateTime)); //agedDate
            
            types.Add("categoryId", typeof(int[])); //categoryid
            types.Add("matchAll", typeof(bool)); //matchAll
            types.Add("maxCount", typeof(int));  //maxcount

            types.Add("pageNumber", typeof(int));
            types.Add("pageSize", typeof(int));
            types.Add("sortBy", typeof(string)); //sortBy
            types.Add("sortDirection", typeof(string)); //sortDirection

            types.Add("isApproved", typeof(bool));//isapproved
            types.Add("isDraft", typeof(bool));//isDraft
            types.Add("keywords", typeof(string)); //keywords
            types.Add("authorId", typeof(int)); //authorId

            types.Add("showPending", typeof(bool)); //showPending
            types.Add("showExpired", typeof(bool)); //showExpired
            types.Add("showFeaturedOnly", typeof(bool)); //showfeaturedonly
            types.Add("showNotFeaturedOnly", typeof(bool)); //shownotfeaturedonly

            types.Add("showSecuredOnly", typeof(bool)); //showSEcuredOnly
            types.Add("showNotSecuredOnly", typeof(bool)); //showNotsecuredonly

            types.Add("articleIds", typeof(string)); //articleIds
            types.Add("totalRecords", typeof(int).MakeByRefType()); //totalrecords
            return types;
        }
        /// <summary>
        /// Returns an instance of a SitePage object based on a single article/tab combination
        /// </summary>
        /// <param name="article">An ArticleInfo object</param>
        /// <param name="tab">the TabInfo object for the page</param>
        /// <returns>A list of SitePage objects, with each one representing a unique URL to an article page</returns>
        private List<SitePage> CreatePagesFromArticle(ArticleInfo article, TabInfo tab, ModuleInfo mod, Locale locale)
        {
            List<SitePage> articlePageURLs = new List<SitePage>();  //new list of SitePages
            //next lot are the controllers/objects for the article page / comments for the article
            PageController pc = new PageController(); 
            CommentController cc = new CommentController();
            ArrayList articlePages = pc.GetPageList(article.ArticleID);
            ArrayList articleComments = cc.GetCommentList(mod.ModuleID,article.ArticleID, true);
            int pageNum = 0;
            //iterate the pages in the article page arrayList
            if (articlePages.Count == 0)
            {
                //still has a url, even if no pages (summary only)
                articlePageURLs.Add(CreateSitePageFromArticlePage(tab, article, 1, articleComments, locale));
            }
            else
            {
                foreach (PageInfo articlePage in articlePages)
                {
                    pageNum++;
                    SitePage articleSitePage = CreateSitePageFromArticlePage(tab, article, pageNum, articleComments, locale);
                    articlePageURLs.Add(articleSitePage);
                    if (_singlePagesOnly) break; //only do the first page if singlepagesOnly
                }
            }
            return articlePageURLs;
        }
        private SitePage CreateSitePageFromArticlePage(TabInfo tab, ArticleInfo article, int pageNum, ArrayList articleComments, Locale locale)
        {
            string url = GetArticleUrl(tab, pageNum, article, locale); //get the URL of the tab/article combination
            DateTime mostRecentUpdate;
            DateTime previousUpdate;
            ChangeFrequencies changeFreq = this.DefaultPageUpdateFrequency;
            if (articleComments.Count > 1)  //check for comments on the article
            {
                //get the latest comment - it is the latest update to the page
                CommentInfo comment = (CommentInfo)articleComments[articleComments.Count - 1];
                //check which is more recent - the article update or the comment
                if (comment.CreatedDate > article.LastUpdate)
                {
                    mostRecentUpdate = comment.CreatedDate;
                    previousUpdate = article.LastUpdate;
                }
                else
                {
                    mostRecentUpdate = article.LastUpdate;
                    previousUpdate = comment.CreatedDate;
                }
            }
            else
            {
                //the most recent update is the difference between the creation and update dates
                mostRecentUpdate = article.LastUpdate;
                previousUpdate = article.CreatedDate;
            }
            //get the default priority for the 'regular' article pages.
            double priority = _defaultPagePriority;
            changeFreq = GetArticleUpdateFrequency(mostRecentUpdate, previousUpdate);
            return new SitePage(url, mostRecentUpdate, changeFreq, priority);
        }

        /// <summary>
        /// Returns the unique URL for the article page
        /// </summary>
        /// <param name="tab">A TabInfo object for the page that the Article Module is on</param>
        /// <param name="pageNum">The page number of the article</param>
        /// <param name="article">The ARticleInfo object for the Article</param>
        /// <returns>A string value, dependant on the Friendly URL setting, of the article</returns>
        private string GetArticleUrl(TabInfo tab, int pageNum, ArticleInfo article, Locale locale)
        {
            string url = base.GetTabUrl(tab);
            string[] queryStringItems;
            string replaceAspxPageName="";
            int queryStringItemsCount = 0;
            if (_useShortUrls)
            {
                if (pageNum == 1)
                {
                    queryStringItemsCount = 2;
                    queryStringItems = new string[2];
                    queryStringItems[0] = "ID";
                    queryStringItems[1] = article.ArticleID.ToString();
                    replaceAspxPageName = Common.FormatTitle(article.Title);
                    if (replaceAspxPageName.Length == 0)
                        replaceAspxPageName = Globals.glbDefaultPage;
                }
                else
                {
                    queryStringItemsCount = 4;
                    queryStringItems = new string[6];
                    queryStringItems[0] = "PageID";
                    queryStringItems[1] = pageNum.ToString();
                    queryStringItems[2] = "ID";
                    queryStringItems[3] = article.ArticleID.ToString();
                    replaceAspxPageName = DotNetNuke.Common.Globals.glbDefaultPage;
                }
            }
            else
            {
                if (pageNum == 1)
                {
                    queryStringItemsCount = 4;
                    queryStringItems = new string[4];
                    queryStringItems[0] = "articleType";
                    queryStringItems[1] = "ArticleView";
                    queryStringItems[2] = "articleID";
                    queryStringItems[3] = article.ArticleID.ToString();
                    replaceAspxPageName = Common.FormatTitle(article.Title);
                    if (replaceAspxPageName.Length == 0)
                        replaceAspxPageName = Globals.glbDefaultPage;
                }
                else
                {
                    queryStringItemsCount = 6;
                    queryStringItems = new string[6];
                    queryStringItems[0] = "PageID";
                    queryStringItems[1] = pageNum.ToString();
                    queryStringItems[2] = "ArticleID";
                    queryStringItems[3] = article.ArticleID.ToString();
                    queryStringItems[4] = "articleType";
                    queryStringItems[5] = "ArticleView";
                    replaceAspxPageName = DotNetNuke.Common.Globals.glbDefaultPage;
                }
            }
            return MakeURL(tab, queryStringItems, replaceAspxPageName, locale);
        }
        /// <summary>
        /// Returns a structured URL with a Querystring based on the input base Tab URL, and each of the name/value pairs in the 
        /// Query string.  This method will automatically determine whether or not friendly URL's are required.
        /// </summary>
        /// <param name="tabURL">The base tab URL for the site : tabname/nn/default.aspx or default.aspx?tabid=55</param>
        /// <param name="queryStringItems">A string array of name value pairs, where [i]=name, [i+1]=value</param>
        /// <param name="aspxPageName">The name to replace default.aspx with, if necessary, otherwise string.empty</param>
        /// <returns>a formatted string of the base URL plus the query string values</returns>
        private string MakeURL(TabInfo tab, string[] queryStringItems, string aspxPageName, Locale locale)
        {
            string url ;
            if (_friendlyURLs == true)
            {
                if (aspxPageName == null || aspxPageName == string.Empty)
                    aspxPageName = DotNetNuke.Common.Globals.glbDefaultPage;
                //build up the path and query string for submitting to the friendly Url provider
                string path = "~/" + DotNetNuke.Common.Globals.glbDefaultPage + "?tabid=" + tab.TabID.ToString();
                bool key=true; string qs=""; string qsPair="";
                foreach (string qsBit in queryStringItems)
                {
                    if (key)
                    {
                        qsPair = qsBit;
                        key = false;
                    }
                    else
                    {
                        qsPair += "=" + qsBit;
                        qs += "&" + qsPair;
                        key = true;
                    }
                }
                path += qs;

                //only add language to url if more than one locale is enabled, and if admin did not turn it off
                if (Localization.GetEnabledLocales().Count > 1 && Localization.UseLanguageInUrl() == true)
                {
                    if (locale == null || locale.Code == "")
                        path += "&language=" + System.Threading.Thread.CurrentThread.CurrentCulture.Name;
                    else
                        path += "&language=" + locale.Code;
                }

                url = DotNetNuke.Services.Url.FriendlyUrl.FriendlyUrlProvider.Instance().FriendlyUrl(tab, path, aspxPageName, _portalSettings);
                
            }
            else
            {
                url = DotNetNuke.Common.Globals.NavigateURL(tab.TabID, "", queryStringItems);
            }
            if (!url.Contains("http://"))
            {
                if (!url.Contains(_portalAliasURL))
                {
                    url = _portalAliasURL + url;
                    if (!url.Contains(_portalAliasURL + "/"))
                        url = url.Replace(_portalAliasURL, _portalAliasURL + "/");
                }
                if (!url.StartsWith("http://"))
                    url = "http://" + url;
            }
            return url;
        }
        /// <summary>
        /// Pulls the tab ID out of a supplied URL.
        /// </summary>
        /// <param name="siteURL">The URL of the tab</param>
        private void StripTabID(string siteURL)
        {
            //we are expecting the siteURL to be a specific tab URL
            string[] urlParts = siteURL.Split('/', '?', '=');
            //example Articles tab ID
            //http://localhost/dnnDev/Articles/tabid/55/Default.aspx
            //http://localhost/dnnDev/Articles/tabid/55/articleType/ArticleView/articleId/1/Test-Article-1.aspx
            //http://localhost/dnnDev/Articles/tabid/55/PageID/2/ArticleID/1/articleType/ArticleView/Default.aspx
            bool nextBitTabID = false, nextBitEntryID = false;
            foreach (string urlBit in urlParts)
            {
                if (nextBitTabID)
                {
                    //this should be the TabID
                    if (int.TryParse(urlBit, out _tabID))
                        nextBitTabID = false;
                }
                switch (urlBit.ToLower())
                {
                    case "tabid":
                        nextBitTabID = true;
                        break;
                }
            }
        }
        /// <summary>
        /// Returns the update frequency of an article, based on the latest entries to it.
        /// </summary>
        /// <param name="mostRecentUpdate">The most recent datetime of an update to the article</param>
        /// <param name="previousUpdate">The second-most recent datetime of an update to the article</param>
        /// <returns>A ChangeFrequencies enumerated type value.</returns>
        private ChangeFrequencies GetArticleUpdateFrequency(DateTime mostRecentUpdate, DateTime previousUpdate)
        {
            ChangeFrequencies changeFreq = ChangeFrequencies.never;
            //work out how often the article is updated by looking at the frequency of the blog updates
            TimeSpan timeBetween = mostRecentUpdate - previousUpdate;
            if (timeBetween.Days < 2)
                changeFreq = ChangeFrequencies.daily;
            else if (timeBetween.Days < 14)
                changeFreq = ChangeFrequencies.weekly;
            else if (timeBetween.Days < 60)
                changeFreq = ChangeFrequencies.monthly;
            else if (timeBetween.Days < 500)
                changeFreq = ChangeFrequencies.yearly;
            else
                changeFreq = ChangeFrequencies.never;
            return changeFreq;
        }
    }
}
